import { DAILY_DISTRIBUTION_COUNT } from '../constants/reward'

const EPSILON = 1e-8

const clampCount = (count: number): number => {
  if (Number.isNaN(count)) {
    return 0
  }
  return Math.min(Math.max(Math.trunc(count), 0), DAILY_DISTRIBUTION_COUNT)
}

const sanitizeAmount = (value: number): number => {
  if (!Number.isFinite(value)) {
    return 0
  }
  return value
}

const roundToFour = (value: number): number => {
  return Number(value.toFixed(4))
}

export interface ManualAdjustmentInput {
  stakedAmount: number
  dailyRewardRate: number
  dailyRewardDistributed: number
  dailyDistributionCount: number
  nextDistributionAt: Date | null
  now: Date
}

export interface ManualAdjustmentResult {
  effectiveDailyReward: number
  targetDailyReward: number
  distributedToday: number
  remainingToday: number
  dailyDistributionCount: number
  nextDistributionAt: Date | null
  scheduleClosedToday: boolean
  cappedDueToReduction: boolean
}

export const calculateManualAdjustmentState = (
  input: ManualAdjustmentInput
): ManualAdjustmentResult => {
  const normalizedStaked = Math.max(0, sanitizeAmount(input.stakedAmount))
  const normalizedRate = Math.max(0, sanitizeAmount(input.dailyRewardRate))
  const normalizedDistributed = Math.max(0, sanitizeAmount(input.dailyRewardDistributed))
  const normalizedCount = clampCount(input.dailyDistributionCount)
  const scheduleClosed = normalizedCount >= DAILY_DISTRIBUTION_COUNT

  const targetDailyReward = roundToFour(normalizedStaked * normalizedRate)
  const effectiveDailyReward = Math.max(targetDailyReward, Number(normalizedDistributed.toFixed(4)))

  const hasRemaining = effectiveDailyReward - normalizedDistributed > EPSILON

  let nextDistributionAt: Date | null = input.nextDistributionAt ? new Date(input.nextDistributionAt) : null
  let finalCount = normalizedCount
  let cappedDueToReduction = false

  if (!hasRemaining) {
    finalCount = DAILY_DISTRIBUTION_COUNT
    nextDistributionAt = null
    cappedDueToReduction = targetDailyReward + EPSILON < normalizedDistributed
  } else if (scheduleClosed) {
    finalCount = DAILY_DISTRIBUTION_COUNT
    nextDistributionAt = null
  } else if (!nextDistributionAt || nextDistributionAt.getTime() <= input.now.getTime()) {
    nextDistributionAt = new Date(input.now.getTime() + 1000)
  }

  if (targetDailyReward + EPSILON < normalizedDistributed) {
    cappedDueToReduction = true
  }

  return {
    effectiveDailyReward: roundToFour(effectiveDailyReward),
    targetDailyReward,
    distributedToday: roundToFour(normalizedDistributed),
    remainingToday: Math.max(0, roundToFour(effectiveDailyReward - normalizedDistributed)),
    dailyDistributionCount: finalCount,
    nextDistributionAt,
    scheduleClosedToday: finalCount >= DAILY_DISTRIBUTION_COUNT,
    cappedDueToReduction
  }
}

const roundCurrency = (value: number): number => {
  return Math.round(value * 100) / 100
}

export interface AssetAdjustmentInput {
  stakedAmountNear: number
  lockedAmountNear: number
  totalRewardsKrw: number
  availableRewardsKrw: number
}

export interface NormalizedAssetAdjustment {
  stakedAmountNear: number
  lockedAmountNear: number
  totalRewardsKrw: number
  availableRewardsKrw: number
}

const sanitizeNear = (value: number): number => {
  if (!Number.isFinite(value) || value < 0) {
    return 0
  }
  return roundToFour(value)
}

const sanitizeCurrency = (value: number): number => {
  if (!Number.isFinite(value) || value < 0) {
    return 0
  }
  return roundCurrency(value)
}

export const normalizeAssetAdjustmentInput = (
  input: AssetAdjustmentInput
): NormalizedAssetAdjustment => {
  return {
    stakedAmountNear: sanitizeNear(input.stakedAmountNear),
    lockedAmountNear: sanitizeNear(input.lockedAmountNear),
    totalRewardsKrw: sanitizeCurrency(input.totalRewardsKrw),
    availableRewardsKrw: sanitizeCurrency(input.availableRewardsKrw)
  }
}
